use std::fs;
use std::fs::DirEntry;
use std::fs::File;
use std::io::{BufRead, BufReader, Error, ErrorKind};
use std::path::Path;
use std::path::PathBuf;

use sqlx::MySql;
use sqlx::Pool;

use tklog::{error, info};

use crate::entity::article_summary::ArticleSummary;

pub struct Extractor<'a> {
    _path: &'a Path,
}

impl Extractor<'_> {
    // one possible implementation of walking a directory only visiting files
    pub async fn visit_dirs(path: &Path, pool: &Pool<MySql>) -> Result<(), Error> {
        info!("-------------visit_dirs start--------------");
        let dir = Path::new(path);
        if dir.is_dir() {
            for entry in fs::read_dir(dir)? {
                let entry = entry?;
                let path = entry.path();
                if path.is_dir() {
                    println!("is dir, loop into sub dir");
                    Box::pin(Self::visit_dirs(&path, pool)).await?;
                } else if path.is_file() {
                    info!("-------------extract start--------------");
                    Extractor::extract(&entry, pool).await?;
                } else {
                    error!("it is not dir or file.");
                }
            }
        }
        Ok(())
    }

    pub async fn extract(dir_entry: &DirEntry, pool: &Pool<MySql>) -> Result<(), Error> {
        let path = dir_entry.path();
        println!("the file name is {:?}", &path.file_name());
        if let Some(extension) = path.extension() {
            let file_suffix = extension
                .to_str()
                .ok_or_else(|| Error::new(ErrorKind::InvalidData, "Invalid file suffix"))?;
            info!("The file extension is: ", &file_suffix);
            if file_suffix.to_lowercase() == "md" {
                info!("-----------md file----------");
                let article_summary = Extractor::extract_aritcle_from_markdown(&path).await?;
                let _ = sqlx::query(
                    r#"INSERT INTO article (title, summary, path, createBy, createOn, modifyBy, modifyOn) VALUES (?, ?, ?,'Admin', now(), 'Admin',now() )"#,
                )
                .bind(&article_summary.title)
                .bind(&article_summary.summary)
				.bind(&article_summary.path)
				.execute(pool).await
				.expect("insert article failed");
            }
        } else {
            eprintln!("Can not find markdown file.");
            error!("Can not find markdown file.");
        }
        Ok(())
    }

    pub async fn extract_aritcle_from_markdown(path: &PathBuf) -> Result<ArticleSummary, Error> {
        let file = File::open(path).map_err(|error| {
            if error.kind() == ErrorKind::NotFound {
                Error::new(
                    ErrorKind::NotFound,
                    format!("The file path doesn't exist: {:?}", path),
                )
            } else {
                error
            }
        });
        let buffer = BufReader::new(file?);
        let mut summary = String::new();
        let mut is_summary = false;
        for line in buffer.lines() {
            let line = line?;
            //读取Summary内容，Summary内容是以Summary为标题（一级，二级，三级标题都可以），直到遇到下一个标题结束。
            if !is_summary && line.trim().contains("#") && line.contains("Summary") {
                is_summary = true;
                continue;
            } else if is_summary && line.trim().contains("#") {
                break; //结束读取
            } else if is_summary {
                //summary 内容
                summary.push_str("\n\t");
                summary.push_str(line.as_str());
            }
        }
        let archives_path = std::env::current_dir().unwrap().join("archives");
        let file_name_with_suffix = path
            .file_name()
            .ok_or_else(|| Error::new(ErrorKind::InvalidInput, "Invalid file name"))?
            .to_str()
            .ok_or_else(|| Error::new(ErrorKind::InvalidData, "File name is not valid UTF-8"))?;
        let file_name_vec: Vec<&str> = file_name_with_suffix.split(".").collect();
        let file_name = file_name_vec[0];
        let article_summary = ArticleSummary::new(
            file_name,
            summary.as_str(),
            format!(
                "{}{}{}",
                &archives_path.to_str().unwrap(),
                "/",
                file_name_with_suffix
            )
            .as_str(),
        );
        println!("{:?}", &article_summary);

        //insert into database
        /*
                        sqlx::query!(
                            r#"INSERT INTO articles (title, summary) VALUES (?, ?)"#,
                            article_summary.title,
                            article_summary.summary
                        )
                        .execute(pool)
                        .await?;
        */
        info!("insert a article summary!");
        //move these files to Archives  directory

        let _ = fs::rename(path, &article_summary.path);
        let _ = fs::remove_file(path);
        Ok(article_summary)
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use sqlx::mysql::MySqlPoolOptions;
    use std::path::Path;

    #[tokio::test]
    async fn test_extract() {
        let path = Path::new("/home/owen/code/rust_project/axum-hello/test/");

        let pool = MySqlPoolOptions::new()
            .connect("mysql://root:rust(202408)@47.92.236.188/Blog")
            .await
            .expect("failed to connect database.");

        for entry in path.read_dir().expect("read_dir call failed") {
            if let Ok(entry) = entry {
                let _ = Extractor::extract(&entry, &pool);
            }
        }
    }
}
